package com.zijidelu.luframework.utils.spring.proxy;

import com.zijidelu.luframework.utils.CastUtil;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.util.ClassUtils;

/**
 * MyBatis-Plus Mapper 工具类
 * <p>
 * 主要用于解决因 Spring AOP 代理导致的无法获取 Mapper 原始接口类型的问题。
 * MyBatis-Plus 在 Spring 环境中注入的 Mapper 实例实际上是一个代理对象，
 * 直接调用 {@code getClass()} 方法会获取到代理类的类型（如 com.sun.proxy.$ProxyXX），
 * 而不是我们定义的 Mapper 接口类型。
 * </p>
 *
 * @author zijidelu
 * @datetime 2025/10/19 12:07
 */
public final class MyBatisPlusMapperProxyUtil {

    /**
     * 私有构造函数，防止工具类被实例化
     */
    private MyBatisPlusMapperProxyUtil() {
        // Utility class
    }

    /**
     * 获取 MyBatis-Plus Mapper 代理对象的原始 Mapper 接口 Class 对象。
     *
     * @param mapperProxy 由 Spring 容器注入的 Mapper 代理实例 (例如，一个 @Autowired 的 UserMapper)
     * @param <R>         Mapper 接口的泛型类型
     * @return 原始 Mapper 接口的 Class 对象
     * @throws IllegalArgumentException 如果传入的对象不是代理对象，或者无法解析出原始接口
     */
    public static <R> Class<R> getOriginalMapperClass(R mapperProxy) {
        if (mapperProxy == null) {
            throw new IllegalArgumentException("Mapper proxy object cannot be null.");
        }

        // 1. 判断是否为代理对象 (对于MyBatis-Plus Mapper，这步几乎总是true)
        if (AopUtils.isAopProxy(mapperProxy)) {
            // 2. 获取代理对象实现的用户自定义接口（排除Spring内部接口）
            Class<?>[] userInterfaces = AopProxyUtils.proxiedUserInterfaces(mapperProxy);

            // MyBatis-Plus 的 Mapper 通常只实现一个接口，就是我们定义的 Mapper 接口
            if (userInterfaces.length > 0) {
                // 安全地强制转换并返回第一个接口
                return CastUtil.cast(userInterfaces[0]);
            }
        }

        // 3. 如果不是代理对象（罕见情况，例如手动new的实例），或者解析失败，则尝试获取其本身的Class
        // 并尝试获取其实现的第一个接口
        Class<?> targetClass = mapperProxy.getClass();
        Class<?>[] interfaces = ClassUtils.getAllInterfacesForClass(targetClass);
        if (interfaces.length > 0) {
            return CastUtil.cast(interfaces[0]);
        }

        // 最后的兜底策略，直接返回实例的Class
        // 这种情况下，返回的可能不是接口，而是实现类
        return CastUtil.cast(targetClass);
    }

}
